package com.wdciti.sccp.iot.gateway.endpoint.my;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.wdciti.sccp.iot.gateway.domain.PersonTagBind;
import com.wdciti.sccp.iot.gateway.domain.Point2D;
import com.wdciti.sccp.iot.gateway.domain.dto.AreaCoordinateDTO;
import com.wdciti.sccp.iot.gateway.enums.AreaShape;
import com.wdciti.sccp.iot.gateway.utils.RedisUtil;
import com.wdciti.sccp.iot.springvertx.annotation.VerticleBean;
import com.wdciti.sccp.iot.utils.PolygonOutlineUtil;
import io.netty.util.internal.StringUtil;
import io.vertx.core.*;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.LocalMap;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.handler.BodyHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.iotdb.rpc.IoTDBConnectionException;
import org.apache.iotdb.rpc.StatementExecutionException;
import org.apache.iotdb.session.Session;
import org.apache.iotdb.session.SessionDataSet;
import org.apache.iotdb.tsfile.read.common.Field;
import org.apache.iotdb.tsfile.read.common.RowRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;


/**
 * @Author xxing
 * @Description 实时定位数据接口
 * @Date 2023/7/18
 **/
@Slf4j
@VerticleBean(isWorker = false)
public class TimePositionDataVerticle extends AbstractVerticle {

    @Value("${vertx.http-port}")
    private int port;
    @Resource
    private Session session;

    public static String PERSON_TAG_LIST_KEY = "person_tag";

    public static String POSITION_KEY = "position";

    public static String RISK_AREA_LIST_KEY = "risk_area";

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 经度1米 = 0.00001141°
     */
    private static double perMetreX = 0.00001141d;
    /**
     * 纬度1米 = 0.00000899°
     */
    private static double perMetreY = 0.00000899d;

    @Value("${plat.writeRiskAreaCacheUrl}")
    private String writeRiskAreaCacheUrl;

    @Override
    public void start() {
        LocalMap<String, JsonObject> personTagCache = vertx.sharedData().getLocalMap("personTagList");
        log.info(" 白名单数量：{}",personTagCache.keySet().size());
        log.info(" 白名单：{}",personTagCache);

        Router router = Router.router(vertx);
        router.route().handler(BodyHandler.create());
        // Create the HTTP server
        vertx.createHttpServer()
                // Handle every request using the router
                .requestHandler(router).exceptionHandler(e -> log.error("Exception", e))
                // Start listening
                .listen(port)
                // Print the port
                .onSuccess(server -> log.info("HTTP server started on port " + server.actualPort()));
        // 定时任务存redis
        vertx.createHttpServer()
                .requestHandler(router)
                .exceptionHandler(e -> log.error("Exception", e))
                .listen(port)
                .onSuccess(server -> log.info("HTTP server started on port " + server.actualPort()));

        // 定时任务存入 Redis
        vertx.setPeriodic(5*1000, 2000, hd -> {
            List<String> ids = new ArrayList<>();
            //log.info("personTagList缓存为：{} ", JSON.toJSONString(personTagCache));
            log.info("白名单数量：{}", personTagCache.keySet().size());
            ids.addAll(personTagCache.keySet());


            JSONArray returnArray = new JSONArray();
            List<String> personIdList = new ArrayList<>();
            ids.forEach(personId -> {
                // 查询数据库取值
                try {
                    JsonObject data = selectLastData(personId);
//                    log.info("iot 查询人员定位数据 personId:{}，结果:{} " ,personId, JSON.toJSONString(data));
//                            log.info("iot 查询数据结果集：{} " , JSON.toJSONString(data));
                    if (!data.isEmpty() && personTagCache.get(personId) != null) {
                        JsonObject bind = personTagCache.get(personId).getJsonObject("pid");
                        String bindStr = JSON.toJSONString(personTagCache.get(personId));
                        JSONObject json = JSONObject.parseObject(bindStr);
                        //PersonTagBind personTagBind = bind.mapTo(PersonTagBind.class);
                        PersonTagBind personTagBind = JSONObject.parseObject(json.getJSONObject("map").getJSONObject("pib").getJSONObject("map").toJSONString(),PersonTagBind.class);
                        data.put("personName", personTagBind.getPersonName());
                        data.put("workTypeName", personTagBind.getWorkTypeName());
                        data.put("workTypeColor", personTagBind.getWorkTypeColor());
                        data.put("identityCard", personTagBind.getIdentityCard());
                        //data.put("workTypeId", personTagBind.getWorkTypeId());
                        // TODO：3.7暂时隐藏时间限制
//                        if (!dataTimeout(data)) {
                        // 判断人员所在的区域
                        //data = addPersonInArea(data);
                        //log.info("单个的人员实时数据：{}", JSON.toJSONString(data));
                        personIdList.add(personId);

                        returnArray.add(JSONObject.parseObject(data.toString()));
//                        }
                    }
                } catch (IoTDBConnectionException | StatementExecutionException e) {
                    log.warn("/position/now 接口 iotdbError:{}", e.getMessage());
                } catch (Exception e) {
                    log.warn("/position/now 接口 error:{}", e.getMessage());
                    e.printStackTrace();
                }
            });
            redisUtil.deleteObject(POSITION_KEY);
            redisUtil.deleteObject(PERSON_TAG_LIST_KEY);
            redisUtil.setCacheObject(POSITION_KEY, personIdList);
            if (!CollectionUtils.isEmpty(returnArray)) {
                redisUtil.setCacheList(PERSON_TAG_LIST_KEY, returnArray);
            }
        });

        // 暴露http接口
        router.post("/position/now").respond(ctx -> {
            JsonObject body = ctx.body().asJsonObject();
            List<String> ids = new ArrayList<>();
            //log.info("personTagList缓存为：{} ", JSON.toJSONString(personTagCache));
            if (body == null || body.getJsonArray("tagId", JsonArray.of()).isEmpty()) {
                ids.addAll(personTagCache.keySet());
            } else {
                body.getJsonArray("tagId").forEach(s -> ids.add(s.toString()));
            }

            JSONArray returnArray = new JSONArray();
            List<String> personIdList = new ArrayList<>();
            ids.forEach(personId -> {
                // 查询数据库取值
                try {
                    JsonObject data = selectLastData(personId);
                    //log.info("iot 查询数据结果集：{} " , JSON.toJSONString(data));
                    if (!data.isEmpty() && personTagCache.get(personId) != null) {
                        JsonObject bind = personTagCache.get(personId).getJsonObject(data.getString("channel"));
                        PersonTagBind personTagBind = bind.mapTo(PersonTagBind.class);
                        data.put("personName", personTagBind.getPersonName());
                        data.put("workTypeName", personTagBind.getWorkTypeName());
                        data.put("workTypeColor", personTagBind.getWorkTypeColor());
                        data.put("workTypeId", personTagBind.getWorkTypeId());
                        // TODO：3.7暂时隐藏时间限制
//                        if (!dataTimeout(data)) {
                            // 判断人员所在的区域
                            data = addPersonInArea(data);
                            //log.info("人员实时数据：{}", JSON.toJSONString(data));
                            personIdList.add(personId);
                            returnArray.add(JSONObject.parseObject(data.toString()));
//                        }
                    }
                } catch (IoTDBConnectionException | StatementExecutionException e) {
                    //log.warn("/position/now 接口 iotdbError:{}", e.getMessage());
                } catch (Exception e) {
                    //log.warn("/position/now 接口 error:{}", e.getMessage());
                    e.printStackTrace();
                }
            });
            redisUtil.deleteObject(POSITION_KEY);
            redisUtil.deleteObject(PERSON_TAG_LIST_KEY);
            redisUtil.setCacheObject(POSITION_KEY, personIdList);
            if (!CollectionUtils.isEmpty(returnArray)) {
                redisUtil.setCacheList(PERSON_TAG_LIST_KEY, returnArray);
            }
            return Future.succeededFuture(returnArray);
        });

        router.post("/position/history").respond(ctx -> {
            MultiMap map = ctx.queryParams();
            String personId = map.get("tagId");
            String startTime = map.get("startTime");
            String endTime = map.get("endTime");
            if (StringUtil.isNullOrEmpty(personId) || StringUtil.isNullOrEmpty(startTime) || StringUtil.isNullOrEmpty(endTime)) {
                return Future.succeededFuture(new JsonObject());
            } else {
                try {
                    JsonObject historyData = selectHistoryData(personId, startTime, endTime);
                    historyData.put("personName", null);
                    historyData.put("workTypeName", null);
                    historyData.put("workTypeColor", null);
                    historyData.put("workTypeId", null);
                    if("1133715002667040768".equals(personId.trim())){
                        historyData.put("personName", "钟超");
                        historyData.put("workTypeName", "普工");
                        historyData.put("workTypeColor", "#f4ea29");
                        return Future.succeededFuture(historyData);
//                                historyData.put("workTypeId", personTagBind.getWorkTypeId());
                    }
                    putBindInfoFromCache(personId,personTagCache,historyData);
                    return Future.succeededFuture(historyData);
                } catch (IoTDBConnectionException | StatementExecutionException e) {
                    return Future.failedFuture(e);
                }
            }
        });
// 查询一段时间内有位置轨迹记录的人员的ID，输入是开始时间，结束时间，输出是人员ID列表
        router.post("/position/history/person").respond(ctx -> {
            MultiMap map = ctx.queryParams();
//            String personId = map.get("tagId");
            String startTime = map.get("startTime");
            String endTime = map.get("endTime");
            if (StringUtil.isNullOrEmpty(startTime) || StringUtil.isNullOrEmpty(endTime)) {
                return Future.succeededFuture(new JsonObject());
            } else {
                JsonArray retJsonArray = new JsonArray();
                personTagCache.keySet().forEach(personId -> {
                    long count = 0;
                    try {
                        count = HasPositionData(personId,startTime,endTime);
                    } catch (IoTDBConnectionException | StatementExecutionException e) {
                        log.warn("查询该人员ID：{} 轨迹记录总数失败，数据库原因：{}", personId, e.getMessage());
                    }
                    if (count > 0) {
                        JsonObject person = JsonObject.of().put("personId",personId);
                        putBindInfoFromCache(personId,personTagCache,person);
                        retJsonArray.add(person);
                    }
                });
                return Future.succeededFuture(new JsonObject().put("code", 200).put("data", retJsonArray));
            }
        });
    }

    private long HasPositionData(String personId, String startTime, String endTime) throws IoTDBConnectionException, StatementExecutionException {
        String path = "root.position.plat.p_" + personId;
        SessionDataSet sessionDataSet = session.executeQueryStatement("select count(channel) from " + path + " where time between " + startTime + " and " + endTime);
        long count = 0;
        while (sessionDataSet.hasNext()) {
            RowRecord record = sessionDataSet.next();
            count = record.getFields().get(0).getLongV();
        }
        return count;
    }

    private JsonObject addPersonInArea(JsonObject personLocation) {

        try {
            // 从redis缓存中取出所有电子围栏
            List<JSONObject> riskAreas = redisUtil.getCacheList(RISK_AREA_LIST_KEY);
            // 如果redis为空，则要调用接口刷新redis
            if (CollectionUtils.isEmpty(riskAreas)) {
                WebClient webClient = WebClient.create(vertx);
                webClient.get(writeRiskAreaCacheUrl).send();
                riskAreas = redisUtil.getCacheList(RISK_AREA_LIST_KEY);
            }
            // 缓存中已有的在场人员
            List<JSONObject> personTags = redisUtil.getCacheList(PERSON_TAG_LIST_KEY);
            if (!CollectionUtils.isEmpty(personTags)) {
                personTags = personTags.stream().filter(obj -> !StringUtils.isEmpty(obj.getString("riskAreaId"))).collect(Collectors.toList());

                // 遍历区域中的人员
                for (JSONObject riskArea : riskAreas) {
                    AreaCoordinateDTO areaCoordinateDTO = new AreaCoordinateDTO(riskArea);
                    boolean inside = oneTimeInside(areaCoordinateDTO, personLocation);
                    // 判断是否第一次进区域, =0 第一次进入 >0 之前存在进入记录
                    List<JSONObject> preData = personTags.stream().filter(obj -> personLocation.getString("tagId").equals(obj.getString("tagId")) && obj.getString("riskAreaId").equals(riskArea.getString("id"))).collect(Collectors.toList());
                    long count = preData.size();
                    //log.info("人员：【{}】，在【{}】区域内，{}", personLocation.getString("personName"), riskArea.getString("areaName"), inside);
                    //log.info("人员：【{}】，在【{}】区域内，第{}次进入！", personLocation.getString("personName"), riskArea.getString("areaName"), count);
                    // 在区域内的话
                    if (inside) {
                        //且第一次进
                        if (count == 0) {
                            personLocation.put("riskAreaId", riskArea.getString("id"));
                            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                            personLocation.put("startTime", sdf.format(new Date()));
                        } else {
                            personLocation.put("riskAreaId", preData.get(0).getString("riskAreaId"));
                            personLocation.put("startTime", preData.get(0).getString("startTime"));
                        }
                        break;
                    } else {
                        // 不在场
                        if (count > 0) {
                            personLocation.putNull("riskAreaId");
                            personLocation.putNull("startTime");
                        }
                    }

                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

        return personLocation;
    }

    /**
     * 单次判断是否进入受限区域
     * params：
     * double pointX 经度
     * double pointY 纬度
     * float extraRange 外扩距离
     */
    public boolean oneTimeInside(AreaCoordinateDTO areaCoordinateDTO, JsonObject location) {
        JSONObject personLocation = JSONObject.parseObject(location.toString());
        //shape 1.长方体  底部是矩形 2.圆柱体 底部是圆形，暂定用 圆形和矩形判断坐标点是否再风险区域内
        if (AreaShape.RECTANGLE.value().equals(areaCoordinateDTO.getShape())) {
            //暂不计算外扩
            List<Point2D> extendedPoints = getRectangleTopList(areaCoordinateDTO);
            return PolygonOutlineUtil.areaContains(extendedPoints, personLocation.getDouble("longitude"), personLocation.getDouble("latitude"));
        } else if (AreaShape.CYLINDER.value().equals(areaCoordinateDTO.getShape())) {
            Point2D centerPoint = new Point2D(areaCoordinateDTO.getXCoordinate(), areaCoordinateDTO.getYCoordinate());
            //暂不计算外扩
            double radius = areaCoordinateDTO.getRadius() + areaCoordinateDTO.getExtraRange();
            Point2D peopleLocation = new Point2D(personLocation.getDouble("longitude"), personLocation.getDouble("latitude"));
            return PolygonOutlineUtil.areaContains(radius, centerPoint, peopleLocation);
        } else {
            return false;
        }
    }

    /**
     * 获取矩形各顶点的坐标集合
     *
     * @param areaCoordinateDTO
     * @return
     */
    private List<Point2D> getRectangleTopList(AreaCoordinateDTO areaCoordinateDTO) {
        // 区域坐标给的中心点坐标，我们根据长宽高映射其他4个点的坐标
        float length = areaCoordinateDTO.getLength() / 2;
        float width = areaCoordinateDTO.getWidth() / 2;

        List<Point2D> pointList = new ArrayList<>();
        //右上 +x+y
        pointList.add(new Point2D(areaCoordinateDTO.getXCoordinate() + perMetreX * (areaCoordinateDTO.getExtraRange() + length), areaCoordinateDTO.getYCoordinate() + perMetreY * (areaCoordinateDTO.getExtraRange() + width)));
        // 左上 -x+y
        pointList.add(new Point2D(areaCoordinateDTO.getXCoordinate() - perMetreX * (length + areaCoordinateDTO.getExtraRange()), areaCoordinateDTO.getYCoordinate() + perMetreY * (areaCoordinateDTO.getExtraRange() + width)));
        // 左下 -x-y
        pointList.add(new Point2D(areaCoordinateDTO.getXCoordinate() - perMetreX * (length + areaCoordinateDTO.getExtraRange()), areaCoordinateDTO.getYCoordinate() - perMetreY * (width + areaCoordinateDTO.getExtraRange())));
        // 右下 +x-y
        pointList.add(new Point2D(areaCoordinateDTO.getXCoordinate() + perMetreX * (areaCoordinateDTO.getExtraRange() + length), areaCoordinateDTO.getYCoordinate() - perMetreY * (width + areaCoordinateDTO.getExtraRange())));
        return pointList;
    }

    private JsonObject selectLastData(String personId) throws IoTDBConnectionException, StatementExecutionException {
        String path = "root.position.pib.p_" + personId;
        SessionDataSet sessionDataSet = session.executeQueryStatement("select last * from " + path);
        //log.info("iot 查询数据结果的sql：{} " , JSON.toJSONString("select last * from " + path));
        JsonObject data = new JsonObject();

        while (sessionDataSet.hasNext()) {
            data.put("tagId", personId);
            RowRecord record = sessionDataSet.next();
            long timestamp = record.getTimestamp();
            data.put("timestamp", timestamp);
            List<Field> fields = record.getFields();
            //指标值
            data.put(fields.get(0).getStringValue().replace(path + ".", ""), fields.get(1).getStringValue());
        }
        return data;
    }

    private JsonObject selectHistoryData(String personId, String startTime, String endTime) throws IoTDBConnectionException, StatementExecutionException {
        JsonObject dataJson = new JsonObject().put("tagId", personId).put("startTime", startTime).put("endTime", endTime).put("coordinate", new JSONArray());
        String path = "root.position.plat.p_" + personId;
        String sql = "select longitude,latitude,high,channel from " + path + " where time between " + startTime + " and " + endTime;
        SessionDataSet sessionDataSet = session.executeQueryStatement(sql);

        while (sessionDataSet.hasNext()) {
            RowRecord record = sessionDataSet.next();
            String longitude = record.getFields().get(0).getStringValue();
            String latitude = record.getFields().get(1).getStringValue();
            String high = record.getFields().get(2).getStringValue();
            String channel = record.getFields().get(3).getStringValue();
            JsonObject dataRecord = JsonObject.of().put("timeStamp", record.getTimestamp()).put("longitude", longitude).put("latitude", latitude).put("high", high).put("channel", channel);
            dataJson.getJsonArray("coordinate").add(dataRecord);
        }
        return dataJson;
    }

    private boolean dataTimeout(JsonObject position) {
        //log.info("systemMs={},positionMs={},channel=ronghe", System.currentTimeMillis(), position.getLong("timestamp"));
//        return false;
        return Math.abs(System.currentTimeMillis() - position.getLong("timestamp")) >= 1000 * 60;
    }

    /**
     * 从personTagCache中获取personId的缓存对象，并提取其中的绑定信息，将其存入historyData中
     *
     * @param personId    人员ID
     * @param personTagCache 人员标签缓存
     * @param historyData 存储绑定信息的Map对象
     */
    public void putBindInfoFromCache(String personId, LocalMap<String, JsonObject> personTagCache, JsonObject historyData) {
        JsonObject cachedBind = personTagCache.get(personId);
        if (cachedBind != null) {
            JsonObject bind = cachedBind.getJsonObject("pib");
            if (bind == null || bind.isEmpty()) {
                bind = cachedBind.getJsonObject("mex");
            }
            if (bind != null) {
                PersonTagBind personTagBind = bind.mapTo(PersonTagBind.class);
                historyData.put("personName", personTagBind.getPersonName());
                historyData.put("workTypeName", personTagBind.getWorkTypeName());
                historyData.put("workTypeColor", personTagBind.getWorkTypeColor());
                historyData.put("workTypeId", personTagBind.getWorkTypeId());
            }
        }
    }

}

